/**
 * Moondance UML modeling tool
 * 
 * @author Dascalu Laurentiu (dascalu.laurentziu@gmail.com).
 * 
 * This program is free software; you can redistribute it and
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package ro.pub.cs.se.moondance.code;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Path;

import ro.pub.cs.se.moondance.MoondanceData;
import ro.pub.cs.se.moondance.UMLPageSerialized;
import ro.pub.cs.se.moondance.model.AggregationConnection;
import ro.pub.cs.se.moondance.model.ClassFunction;
import ro.pub.cs.se.moondance.model.ClassVariable;
import ro.pub.cs.se.moondance.model.CompositionConnection;
import ro.pub.cs.se.moondance.model.Connection;
import ro.pub.cs.se.moondance.model.Parameter;
import ro.pub.cs.se.moondance.model.UMLBaseClass;
import ro.pub.cs.se.moondance.model.UMLClass;
import ro.pub.cs.se.moondance.model.UMLDiagram;
import ro.pub.cs.se.moondance.model.UMLEntity;
import ro.pub.cs.se.moondance.model.UMLInterface;
import ro.pub.cs.se.moondance.model.UMLPackage;
import ro.pub.cs.se.moondance.model.VisibilityType;

/**
 * C++ code generator
 */
public class CPPCodeGenerator extends ICodeGenerator {
	// C++ source file extension
	private final static String CPP_SOURCE = ".cpp";

	// C++ header file extension
	private final static String CPP_HEADER = ".hpp";

	// source folder
	private final static String CPP_SRC = "src/";

	// header floder
	private final static String CPP_INCLUDE = "include/";

	private IProject iProj = null;

	private static String[] groupByVisibility = { VisibilityType.PUBLIC,
			VisibilityType.PROTECTED, VisibilityType.PRIVATE };

	/**
	 * C++ code generation format:
	 * 
	 * === C++ HEADER FILE FORMAT ===
	 * 
	 * == CLASS == class $CLASS_NAME$: public $CLASS_INHERITANCE_1$, ..,
	 * $CLASS_INHERITANCE_N$ {
	 * 
	 * = methods =
	 * 
	 * visibility: $CLASS_NAME$($PARAM_1$, .., $PARAM_N$); $ABSTRACT$
	 * ~$CLASS_NAME$(); $STATIC$ $RETURN_TYPE$ $FUNCTION_NAME$($PARAM_1$, ..,
	 * $PARAM_N$); $ABSTRACT$ $RETURN_TYPE$ $FUNCTION_NAME$($PARAM_1$, ..,
	 * $PARAM_N$);
	 * 
	 * = attributes = visibility: $STATIC$ $VARIABLE_NAME$ = $DEFAULT_VALUE$;
	 * 
	 * }
	 * 
	 * == ABSTRACT CLASS == -> Same as above, but all the non-static methods are
	 * declared virtual
	 * 
	 * == INTERFACE == -> Contains only pure virtual methods
	 * 
	 * class $CLASS_NAME$ {
	 * 
	 * = methods = public: virtual $RETURN_TYPE$ $FUNCTION_NAME$($PARAM_1$,..,
	 * $PARAM_N$) = 0;
	 * 
	 * }
	 * 
	 * == NAMESPACE ==
	 * 
	 * namespace $NAMESPACE_NAME$ { Content, based on class, abstract class,
	 * interface or another namespace }
	 * 
	 * 
	 * === C++ SOURCE FILE FORMAT ===:
	 * 
	 * == CLASS == #include "include/$CLASS_NAME$"
	 * $CLASS_NAME$::$CLASS_NAME$($PARAM_1$, .., $PARAM_N$) { }
	 * 
	 * $CLASS_NAME$::~$CLASS_NAME$() { }
	 * 
	 * $STATIC$ $RETURN_TYPE$ $CLASS_NAME$::$FUNCTION_NAME$($PARAM_1$, ..,
	 * $PARAM_N$) { }
	 * 
	 * == ABSTRACT_CLASS == As above.
	 * 
	 * == INTERFACE_CLASS == No source file
	 */

	@Override
	protected void createFolders() throws Exception {
		/**
		 * Creates project/src and project/src/include
		 */
		if (iProj instanceof IContainer) {
			IContainer container = (IContainer) iProj;

			IFolder folder = container.getFolder(new Path(CPP_SRC));
			if (!folder.exists())
				folder.create(true, true, MoondanceData.DEFAULT_MONITOR);

			folder = container.getFolder(new Path(CPP_SRC + CPP_INCLUDE));
			if (!folder.exists())
				folder.create(true, true, MoondanceData.DEFAULT_MONITOR);

		} else
			throw new Exception();
	}

	private String variableToString(ClassVariable cVar) {
		String data = "    ";

		if (cVar.isStatic())
			data += "static ";
		data += cVar.getType();

		if (cVar.getMultiplicity() >= 0)
			data += "[" + cVar.getMultiplicity() + "]";
		data += " ";

		data += cVar.getName();

		if (cVar.getInitialValue() != null
				&& !cVar.getInitialValue().equals(""))
			data += " = " + cVar.getInitialValue() + ";\n";
		else
			data += ";\n";

		return data;
	}

	private String functionHeaderToString(ClassFunction cFun,
			boolean isClassAbstract, boolean isInterface) {
		String data = "    ";

		if (cFun.isConstructor()) {
			// Constructor
			data += cFun.getName() + "(";

			ArrayList<Parameter> params = cFun.getParameters();

			for (int i = 0; i < params.size(); i++) {
				Parameter param = params.get(i);
				data += param.getType();

				if (param.getMultiplicity())
					data += "[]";
				data += " " + param.getName();
				if (i != params.size() - 1)
					data += ", ";
			}

			data += ");\n";
		} else if (cFun.isDestructor())
			// Destructor
			data += "~" + cFun.getName() + "();\n";
		else {
			if (cFun.isAbstract() || isInterface || isClassAbstract)
				data += "virtual ";
			// Ordinary function
			if (cFun.isStatic())
				data += "static ";
			data += cFun.getReturnType() + " " + cFun.getName() + "(";

			ArrayList<Parameter> params = cFun.getParameters();

			for (int i = 0; i < params.size(); i++) {
				Parameter param = params.get(i);
				data += param.getType();

				if (param.getMultiplicity())
					data += "[]";
				data += " " + param.getName();
				if (i != params.size() - 1)
					data += ", ";
			}

			if (isInterface)
				data += ") = 0;\n";
			else
				data += ");\n";
		}
		return data;
	}

	private String functionSourceToString(ClassFunction cFun,
			String currentClass) {
		String data = "";

		if (cFun.isConstructor()) {
			// Constructor
			data += cFun.getName() + "::" + cFun.getName() + "(";

			ArrayList<Parameter> params = cFun.getParameters();

			for (int i = 0; i < params.size(); i++) {
				Parameter param = params.get(i);
				data += param.getType();

				if (param.getMultiplicity())
					data += "[]";
				data += " " + param.getName();
				if (i != params.size() - 1)
					data += ", ";
			}

			data += "){\n}\n\n";
		} else if (cFun.isDestructor())
			// Destructor
			data += cFun.getName() + "::~" + cFun.getName() + "()\n{\n}\n\n";
		else {
			data += cFun.getReturnType() + " " + currentClass + "::"
					+ cFun.getName() + "(";

			ArrayList<Parameter> params = cFun.getParameters();

			for (int i = 0; i < params.size(); i++) {
				Parameter param = params.get(i);
				data += param.getType();

				if (param.getMultiplicity())
					data += "[]";
				data += " " + param.getName();
				if (i != params.size() - 1)
					data += ", ";
			}
			data += ")\n{\n}\n\n";
		}

		return data;
	}

	private String generateClassHeaderCode(UMLClass myClass) {
		String dataHeader;
		String additionalInclude = "";

		dataHeader = "class " + myClass.getName();

		ArrayList<UMLInterface> interfaces = myClass.getImplementedInterfaces();
		ArrayList<UMLClass> classes = myClass.getExtendedClasses();

		if (interfaces.size() + classes.size() > 0)
			dataHeader += ": ";
		for (int i = 0; i < interfaces.size() + classes.size(); i++) {
			String addClass = null;

			if (i < classes.size())
				addClass = classes.get(i).getName();
			else
				addClass = interfaces.get(i - classes.size()).getName();

			dataHeader += "public " + addClass;
			additionalInclude += "#include " + "\"" + addClass + ".hpp\"\n";
			if (i < interfaces.size() + classes.size() - 1)
				dataHeader += ", ";
		}

		dataHeader += "\n{\n";

		ArrayList<String> references = new ArrayList<String>();

		// Add aggregation/composition members, if any
		for (int i = 0; i < myClass.getTargetConnections().size(); i++) {
			Connection conn = myClass.getTargetConnections().get(i);
			if (conn instanceof CompositionConnection
					|| conn instanceof AggregationConnection) {
				references.add(conn.getSource().toString());
			}
		}

		if (references.size() > 0) {
			for (int i = 0; i < references.size(); i++)
				additionalInclude += "#include " + "\"" + references.get(i)
						+ ".hpp\"\n";

			dataHeader += "// Autogenerated class references to this aggregate/composite class\n";
			dataHeader += "private:\n";

			for (int i = 0; i < references.size(); i++)
				dataHeader += references.get(i) + " *" + "refence"
						+ references.get(i) + ";\n";
			dataHeader += "\n\n";
		}

		// Add the members grouped by visibility
		ArrayList<ClassVariable> cVars = myClass.getAttributes();

		for (int j = 0; j < groupByVisibility.length; j++) {
			dataHeader += groupByVisibility[j] + ":\n";
			for (int i = 0; i < cVars.size(); i++)
				if (cVars.get(i).getVisibility().equals(groupByVisibility[j]))
					dataHeader += variableToString(cVars.get(i));
			dataHeader += "\n";
		}

		// Add the methods
		ArrayList<ClassFunction> cFuns = myClass.getOperations();

		for (int j = 0; j < groupByVisibility.length; j++) {
			dataHeader += groupByVisibility[j] + ":\n";
			for (int i = 0; i < cFuns.size(); i++)
				if (cFuns.get(i).getVisibility().equals(groupByVisibility[j]))
					dataHeader += functionHeaderToString(cFuns.get(i), cFuns
							.get(i).isAbstract(), false);
			dataHeader += "\n";
		}

		dataHeader += "};\n";

		return additionalInclude + "\n" + dataHeader;
	}

	private String generateInterfaceHeaderCode(UMLInterface myInterface) {
		String dataHeader = "class " + myInterface.getName() + "\n{\n";

		// Add the members
		ArrayList<ClassVariable> cVars = myInterface.getAttributes();

		for (int i = 0; i < cVars.size(); i++)
			dataHeader += variableToString(cVars.get(i));

		// Add the methods
		ArrayList<ClassFunction> cFuns = myInterface.getOperations();

		for (int i = 0; i < cFuns.size(); i++)
			dataHeader += functionHeaderToString(cFuns.get(i), true, true);

		dataHeader += "};\n";

		return dataHeader;
	}

	private String generateClassSourceCode(UMLClass myClass,
			boolean shouldInclude) {
		String dataSource = "";

		if (shouldInclude)
			dataSource = "#include \"include/" + myClass.getName() + ".hpp"
					+ "\"\n\n";

		// Add the methods
		ArrayList<ClassFunction> cFuns = myClass.getOperations();

		for (int i = 0; i < cFuns.size(); i++) {
			dataSource += functionSourceToString(cFuns.get(i), myClass
					.getName());
		}

		return dataSource;
	}

	private String generateNamespaceHeaderCode(UMLPackage myPackage) {
		String dataHeader = "namespace " + myPackage.getName() + "\n{\n";

		// Generate code for other classes
		for (int i = 0; i < myPackage.getContainedClasses().size(); i++) {
			UMLBaseClass baseClass = myPackage.getContainedClasses().get(i);

			if (baseClass instanceof UMLInterface)
				dataHeader += generateInterfaceHeaderCode((UMLInterface) baseClass);
			else if (baseClass instanceof UMLClass)
				dataHeader += generateClassHeaderCode((UMLClass) baseClass);
		}

		// Generate code for other packages
		for (int i = 0; i < myPackage.getContainedPackages().size(); i++)
			dataHeader += "\n"
					+ generateNamespaceHeaderCode(myPackage
							.getContainedPackages().get(i)) + "\n";

		dataHeader += "}\n\n";

		return dataHeader;
	}

	private String generateNamespaceSourceCode(UMLPackage myPackage) {
		String dataSource = "namespace " + myPackage.getName() + "\n{\n";

		// Generate code for other classes
		for (int i = 0; i < myPackage.getContainedClasses().size(); i++) {
			UMLBaseClass baseClass = myPackage.getContainedClasses().get(i);

			if (baseClass instanceof UMLClass)
				dataSource += generateClassSourceCode((UMLClass) baseClass,
						false);
		}

		// Generate code for other packages
		for (int i = 0; i < myPackage.getContainedPackages().size(); i++)
			dataSource += generateNamespaceSourceCode(myPackage
					.getContainedPackages().get(i));

		dataSource += "}\n\n";

		return dataSource;
	}

	private void generateCode(UMLEntity block) throws Exception {
		String name = block.toString();
		String dataSource = null, dataHeader = null;

		String headerFile = CPP_SRC + CPP_INCLUDE + name + CPP_HEADER;
		String sourceFile = CPP_SRC + name + CPP_SOURCE;

		if (block instanceof UMLClass) {
			dataHeader = generateClassHeaderCode((UMLClass) block);
			dataSource = generateClassSourceCode((UMLClass) block, true);
		} else if (block instanceof UMLInterface) {
			dataHeader = generateInterfaceHeaderCode((UMLInterface) block);
		} else if (block instanceof UMLPackage) {
			dataSource = "#include \"include/" + ((UMLPackage) block).getName()
					+ ".hpp" + "\"\n\n";
			dataSource += generateNamespaceSourceCode((UMLPackage) block);
			dataHeader = generateNamespaceHeaderCode((UMLPackage) block);
		}

		if (dataHeader != null)
			createNewFile(iProj, headerFile, dataHeader.getBytes());

		if (dataSource != null)
			createNewFile(iProj, sourceFile, dataSource.getBytes());
	}

	@Override
	public void generateCode() {
		/**
		 * From the project name I eliminate the extension and given that name
		 * to the new created project.
		 * 
		 * Example: Example1.uml.moondance => Example1
		 */
		String projectName = MoondanceData.getCurrentDiagram().getDiagramName();
		projectName = projectName.substring(0, projectName.length()
				- UMLPageSerialized.DEFAULT_EXTENSION.length());

		iProj = ResourcesPlugin.getWorkspace().getRoot()
				.getProject(projectName);

		try {
			if (iProj.exists())
				iProj.delete(true, MoondanceData.DEFAULT_MONITOR);
			iProj.create(MoondanceData.DEFAULT_MONITOR);
			iProj.open(MoondanceData.DEFAULT_MONITOR);

			// Create source files folder (src/)
			// and header files folder (src/include/)
			createFolders();

			// For-each block (class, abstract class and interface)
			// generate a pair (source file, header file)
			// Generation involves: file creation, block code generation
			// and linking between blocks (inheritance etc)
			UMLDiagram diagram = MoondanceData.getCurrentDiagram();

			List<UMLEntity> blocksList = diagram.getChildren();

			for (int i = 0; i < blocksList.size(); i++)
				generateCode(blocksList.get(i));

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		generateCode();
	}
}
